#include <crt_util.h>

atomic_type xadd(atomic_type* ptr)
{
#if (TARGET_OS == OS_POSIX)
    __sync_add_and_fetch(ptr, 1);
    return *ptr;
#elif (TARGET_OS == OS_WIN)

#if (TARGET_BITS == ARCH_64BIT)
    return InterlockedExchangeAdd64(ptr, 1);
#elif (TARGET_BITS == ARCH_32BIT)
    return InterlockedExchangeAdd(ptr, 1);
#endif

#elif (TARGET_OS == OS_MACH)
    // atomic_type new_value = *ptr + 1;
    // OSAtomicCompareAndSwap32Barrier(*ptr, new_value, ptr);
    // return *ptr;
    return OSAtomicIncrement32Barrier(ptr);
#elif (TARGET_OS == OS_UNIX)
    __sync_add_and_fetch(ptr, 1);
    return *ptr;
#endif
    return 0;
}

atomic_type xdel(atomic_type* ptr)
{
#if (TARGET_OS == OS_POSIX)
    __sync_sub_and_fetch(ptr, 1);
    return *ptr;
#elif (TARGET_OS == OS_WIN)

#if (TARGET_BITS == ARCH_64BIT)
    return InterlockedExchangeAdd64(ptr, -(ssize_t)(1));
#elif (TARGET_BITS == ARCH_32BIT)
    return InterlockedExchangeAdd(ptr, -(ssize_t)(1));
#endif

#elif (TARGET_OS == OS_MACH)
    // atomic_type new_value = *ptr - 1;
    // OSAtomicCompareAndSwap32Barrier(*ptr, new_value, ptr);
    // return *ptr;
    return OSAtomicDecrement32Barrier(ptr);
#elif (TARGET_OS == OS_UNIX)
    __sync_sub_and_fetch(ptr, 1);
    return *ptr;
#endif
    return 0;
}

atomic_type _atomic_init(_atomic_t* self)
{
    _mutex_init(&self->lock);
#if (TARGET_OS == OS_POSIX)
    __sync_lock_test_and_set(&self->value, 0);
    return self->value;
#elif (TARGET_OS == OS_WIN)

#if (TARGET_BITS == ARCH_64BIT)
    return InterlockedExchange64(&self->value, 0);
#elif (TARGET_BITS == ARCH_32BIT)
    return InterlockedExchange(&self->value, 0);
#endif

#elif (TARGET_OS == OS_MACH)
    self->value = 0;
    OSAtomicCompareAndSwap32Barrier(self->value, 0, &self->value);
    return self->value;
#elif (TARGET_OS == OS_UNIX)
    __sync_lock_test_and_set(&self->value, 0);
    return self->value;
#endif

    return S_SUCCESS;
}

atomic_type _atomic_uninit(_atomic_t* self)
{
    _mutex_destroy(&self->lock);
#if (TARGET_OS == OS_POSIX)
    __sync_lock_test_and_set(&self->value, 0);
    return self->value;
#elif (TARGET_OS == OS_WIN)

#if (TARGET_BITS == ARCH_64BIT)
    return InterlockedExchange64(&self->value, 0);
#elif (TARGET_BITS == ARCH_32BIT)
    return InterlockedExchange(&self->value, 0);
#endif

#elif (TARGET_OS == OS_MACH)
    self->value = 0;
    OSAtomicCompareAndSwap32Barrier(self->value, 0, &self->value);
    return self->value;

#elif (TARGET_OS == OS_UNIX)
    __sync_lock_test_and_set(&self->value, 0);
    return self->value;
#endif
}

atomic_type _atomic_add(_atomic_t* self)
{
#if (TARGET_OS == OS_POSIX)
    __sync_add_and_fetch(&self->value, 1);
    return self->value;
#elif (TARGET_OS == OS_WIN)

#if (TARGET_BITS == ARCH_64BIT)
    return InterlockedExchangeAdd64(&self->value, 1);
#elif (TARGET_BITS == ARCH_32BIT)
    return InterlockedExchangeAdd(&self->value, 1);
#endif

#elif (TARGET_OS == OS_MACH)
    // atomic_type new_value = self->value;
    // xadd(&new_value);
    // OSAtomicCompareAndSwap32Barrier(self->value, new_value, &self->value);
    // return self->value;
    return xadd(&self->value);
#elif (TARGET_OS == OS_UNIX)
    __sync_add_and_fetch(&self->value, 1);
    return self->value;
#endif

    return S_SUCCESS;
}

atomic_type _atomic_del(_atomic_t* self)
{
#if (TARGET_OS == OS_POSIX)
    __sync_sub_and_fetch(&self->value, 1);
    return self->value;
#elif (TARGET_OS == OS_WIN)

#if (TARGET_BITS == ARCH_64BIT)
    return InterlockedExchangeAdd64(&self->value, -(ssize_t)(1));
#elif (TARGET_BITS == ARCH_32BIT)
    return InterlockedExchangeAdd(&self->value, -(ssize_t)(1));
#endif

#elif (TARGET_OS == OS_MACH)
    // atomic_type new_value = self->value;
    // xdel(&new_value);
    // OSAtomicCompareAndSwap32Barrier(self->value, new_value, &self->value);
    // return self->value;
    return xdel(&self->value);
#elif (TARGET_OS == OS_MACH)
    __sync_sub_and_fetch(&self->value, 1);
    return self->value;
#endif
    return S_SUCCESS;
}

atomic_type _atomic_set(atomic_type set, _atomic_t* self)
{
    atomic_type value = 0;
    _mutex_lock(&self->lock);
    value = self->value;
    _mutex_unlock(&self->lock);

#if (TARGET_OS == OS_WIN)
    _InterlockedCompareExchange(&self->value, set, value);
#elif (TARGET_OS == OS_POSIX)

#if defined(__GNUC__) && (__GNUC__ >= 4)
    __sync_bool_compare_and_swap(&self->value, value, set);
#else
    _mutex_lock(&self->lock);
    self->value = set;
    _mutex_unlock(&self->lock);
#endif

#elif (TARGET_OS == OS_MACH)
    OSAtomicCompareAndSwap32Barrier(value, set, &self->value);
#elif (TARGET_OS == OS_UNIX)
#if defined(__GNUC__) && (__GNUC__ >= 4)
    __sync_bool_compare_and_swap(&self->value, value, set);
#else
    _mutex_lock(&self->lock);
    self->value = set;
    _mutex_unlock(&self->lock);
#endif

#endif

    return self->value;
}

int _atomic_cmp(atomic_type comp, _atomic_t* self)
{
    atomic_type value = 0;

#if (TARGET_OS == OS_WIN)
    atomic_type original = 0;
    _mutex_lock(&self->lock);
    value = self->value;
    _mutex_unlock(&self->lock);

    original = _InterlockedCompareExchange(&self->value, value, comp);
    return (original == comp);
#elif (TARGET_OS == OS_POSIX)
    atomic_type original = 0;
#if defined(__GNUC__) && (__GNUC__ >= 4)
    value = self->value;
    return __sync_bool_compare_and_swap(&self->value, comp, value);
#else
    _mutex_lock(&self->lock);
    original = self->value;
    _mutex_unlock(&self->lock);

    return comp == original;
#endif
#elif (TARGET_OS == OS_MACH)

    _mutex_lock(&self->lock);
    value = self->value;
    _mutex_unlock(&self->lock);

    return OSAtomicCompareAndSwap32Barrier(comp, value, &self->value);

#elif (TARGET_OS == OS_UNIX)

    atomic_type original = 0;
#if defined(__GNUC__) && (__GNUC__ >= 4)
    value = self->value;
    return __sync_bool_compare_and_swap(&self->value, comp, value);
#else
    _mutex_lock(&self->lock);
    original = self->value;
    _mutex_unlock(&self->lock);

    return comp == original;
#endif
#endif
}
